package com.suncky.frame.http.file;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class Downloader {
    private String url;
    private String destDir;
    private String fileName;
    private DownloadCallback<File> downloadCallBack;
    private ContentLengthCallback contentLengthCallback;
    private OkHttpClient okHttpClient;
    private final MainThreadExecutor executor;

    private boolean isDownloading = false;
    private boolean isCanceled = false;
    private boolean isPaused = false;

    private Downloader(Builder builder) {
        this.url = builder.url;
        this.destDir = builder.destDir;
        this.fileName = builder.fileName;// != null ? builder.fileName : getUrlFileName(this.url);
        this.downloadCallBack = builder.downloadCallBack;
        this.contentLengthCallback = builder.contentLengthCallback;
        this.okHttpClient = builder.okHttpClient;
        this.executor = new MainThreadExecutor(builder.context);
    }

    public String getUrl() {
        return url;
    }

    public String getFileName() {
        return fileName;
    }

    public String getDestDir() {
        return destDir;
    }

    public static class Builder{
        private final Context context;
        private String url;
        private String destDir;
        private String fileName;
        private long timeout = 20;//超时时间
        private DownloadCallback<File> downloadCallBack;
        private ContentLengthCallback contentLengthCallback;
        private OkHttpClient okHttpClient;

        public Builder(Context context) {
            this.context = context;
        }

        public Builder url(String url){
            this.url = url;
            return this;
        }
        public Builder destDir(String destDir){
            this.destDir = destDir;
            return this;
        }

        /**
         * 设置下载保存的文件名，不设置将使用默认文件名
         * @param fileName
         * @return
         */
        public Builder fileName(String fileName){
            this.fileName = fileName;
            return this;
        }
        public Builder downloadCallBack(DownloadCallback<File> callBack){
            this.downloadCallBack = callBack;
            return this;
        }
        public Builder contentLengthCallback(ContentLengthCallback callBack){
            this.contentLengthCallback = callBack;
            return this;
        }
        public Builder okHttpClient(OkHttpClient okHttpClient){
            this.okHttpClient = okHttpClient;
            return this;
        }

        /**
         * 设置超时时间(秒)
         * @param timeout 超时时间(秒)
         * @return
         */
        public Builder timeOut(long timeout) {
            this.timeout = timeout;
            return this;
        }

        public Downloader build() {
            if (okHttpClient == null) {
                okHttpClient = getDefaultOkHttpClient();
            }
            return new Downloader(this);
        }

        private OkHttpClient getDefaultOkHttpClient() {
            return new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS)
                    .readTimeout(timeout, TimeUnit.SECONDS).writeTimeout(timeout, TimeUnit.SECONDS).
                            build();
        }
    }

    /**
     * 下载文件，覆盖已存在的文件
     */
    public void download() {
        download(false, downloadCallBack);
    }

    /**
     * 下载文件，覆盖已存在的文件
     * @param callBack
     */
    public void download(DownloadCallback<File> callBack) {
        download(false, callBack);
    }

    /**
     * 下载文件
     * @param append true-在已存在的文件末尾继续下载 false-覆盖已存在的文件
     * @param callBack
     */
    public void download(boolean append, DownloadCallback<File> callBack) {
        if (isDownloading) {
            return;
        }
        isDownloading = true;
        isCanceled = false;
        isPaused = false;
        downloadCallBack = callBack;
        if (append) {
            getContentLength(true, contentLengthCallback);
        } else {
            if (callBack != null) {
                executor.execute(callBack::onStart);
            }
            Request request = new Request.Builder().get().url(url).build();
            okHttpClient.newCall(request).enqueue(new DownloadCallbackWrapper(false, callBack));
        }
    }

    /**
     * 获取下载文件大小
     */
    public void getContentLength() {
        //获取文件大小
        getContentLength(contentLengthCallback);
    }

    /**
     * 获取下载文件大小
     */
    public void getContentLength(ContentLengthCallback callback) {
        getContentLength(false, callback);
    }


    /**
     * 获取下载文件大小
     * @param download true-成功破那个获取文件大小之后下载文件  false-仅获取下载文件大小
     * @param callback
     */
    private void getContentLength(boolean download, ContentLengthCallback callback) {
        contentLengthCallback = callback;
        if (callback != null) {
            executor.execute(callback::onStart);
        }
        if (download && downloadCallBack != null) {
            executor.execute(()->downloadCallBack.onStart());

        }
        //获取文件大小
        Request lengthRequest = new Request.Builder().get().url(url).head().build();
        okHttpClient.newCall(lengthRequest).enqueue(new ContentLengthCallbackWrapper(download, callback));
    }

    /**
     * 暂停下载
     */
    public void pause() {
        if (isCanceled || !isDownloading) {
            return;
        }
        isDownloading = false;
        isPaused = true;
    }

    /**
     * 继续下载
     */
    public void resume() {
        download(true, downloadCallBack);
    }

    /**
     * 取消下载
     */
    public void cancel() {
        if (!isDownloading) {
            if (downloadCallBack != null) {
                executor.execute(()->downloadCallBack.onCanceled());
            }
        } else {
            isDownloading = false;
        }
        isCanceled = true;
        deleteDestFile();
    }

    /**
     * 开始下载
     */
    public void start() {
        download();
    }

    /**
     * 删除目标文件
     */
    public boolean deleteDestFile() {
        if (fileName == null) {
            return false;
        }
        File file = new File(destDir, fileName);
        if (file.exists()) {
            return file.delete();
        }
        return true;
    }

    public boolean isDownloading() {
        return isDownloading;
    }

    public boolean isCanceled() {
        return isCanceled;
    }

    public boolean isPaused() {
        return isPaused;
    }

    class DownloadCallbackWrapper implements Callback {

        private final DownloadCallback<File> callBack;
        private final boolean append;

        public DownloadCallbackWrapper(boolean append, DownloadCallback<File> callBack) {
            this.callBack = callBack != null ? callBack : new DownloadCallback<File>() {
                @Override
                public void onProgress(long progress, long total) {

                }

                @Override
                public void onSuccess(File result) {

                }

                @Override
                public void onFailure(String message) {

                }
            };
            this.append = append;
        }

        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            isDownloading = false;
            executor.execute(() -> callBack.onFailure(e.getMessage()));
        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            int code = response.code();
            if (append&&code==206) {
                //针对范围请求，响应会返回状态码为206 Partial Content的响应报文。如果服务端无法响应范围请求，则会返回状态码200 OK和完整的实体内容。
                saveFile(response, true);
            }else if (code == 200) {
                saveFile(response, false);
            } else {
                isDownloading = false;
                executor.execute(() -> callBack.onFailure(response.message()));
            }
        }

        private void saveFile(@NonNull Response response, boolean append) {
            ResponseBody body = response.body();
            if (body == null) {
                isDownloading = false;
                executor.execute(() -> callBack.onFailure("ResponseBody is null"));
                return;
            }

            if (TextUtils.isEmpty(destDir)) {
                isDownloading = false;
                executor.execute(() -> callBack.onFailure("未指定下载目录"));
                return;
            }
            File dir = new File(destDir);
            if ((!dir.exists() || !dir.isDirectory()) && !dir.mkdirs()) {
                isDownloading = false;
                executor.execute(() -> callBack.onFailure("下载目录不存在"));
                return;
            }
            InputStream is = null;
            byte[] buf = new byte[4096];
            int len;
            FileOutputStream fos = null;
            try {
                is = body.byteStream();
                long sum = 0;
                if (TextUtils.isEmpty(fileName)) {
                    fileName = getDefaultFileName(response);
                }
                File file = new File(destDir, fileName);
                boolean exists = file.exists();
                if (exists) {
                    if (append) {
                        sum = file.length();
                    } else {
                        file.delete();
                    }
                }
                final long total = body.contentLength() + sum;
                fos = new FileOutputStream(file, append && exists);
                while (!isCanceled && !isPaused && (len = is.read(buf)) != -1) {
                    sum += len;
                    fos.write(buf, 0, len);
                    executor.execute(new ProgressRunnable(sum, total));
                }
                if (!isCanceled) {
                    fos.flush();
                    if (!isPaused) {
                        isDownloading = false;
                        executor.execute(() -> callBack.onSuccess(file));
                    }
                } else {
                    isDownloading = false;
                    executor.execute(callBack::onCanceled);
                }
            } catch (Exception e) {
                isDownloading = false;
                executor.execute(() -> callBack.onFailure(e.getMessage()));
            } finally {
                try {
                    body.close();
                    if (is != null) is.close();
                    if (fos != null) fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        class ProgressRunnable implements Runnable {

            private final long progress;
            private final long total;

            ProgressRunnable(long progress, long total) {
                this.progress = progress;
                this.total = total;
            }

            @Override
            public void run() {
                callBack.onProgress(progress, total);
            }
        }
    }

    class ContentLengthCallbackWrapper implements Callback {
        private final ContentLengthCallback callback;
        private final boolean download;

        public ContentLengthCallbackWrapper(boolean download,ContentLengthCallback callback) {
            this.callback = callback != null ? callback : new ContentLengthCallback() {
                @Override
                public void onSuccess(long length) {

                }

                @Override
                public void onFailure(String message) {

                }
            };
            this.download = download;
        }

        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            isDownloading = false;
            executor.execute(() -> this.callback.onFailure(e.getMessage()));
            if (download && downloadCallBack != null) {
                executor.execute(() -> downloadCallBack.onFailure(e.getMessage()));
            }
        }

        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            if (response.code() == 200) {
                String contentLength = response.header("Content-Length");
                if (!TextUtils.isEmpty(contentLength)) {
                    try {
                        long length = Long.parseLong(contentLength);
                        executor.execute(() -> this.callback.onSuccess(length));
                        if (download) {
                            if (isPaused) {
                                return;
                            }
                            if (isCanceled) {
                                if (downloadCallBack != null) {
                                    executor.execute(() -> downloadCallBack.onCanceled());
                                }
                                return;
                            }
                            long startByte = 0;
                            if (TextUtils.isEmpty(fileName)) {
                                fileName = getDefaultFileName(response);
                            }
                            File file = new File(destDir, fileName);
                            if (file.exists()) {
                                startByte = file.length();
                            }
                            if (startByte < length) {
                                Request request = new Request.Builder().get().url(url)
                                        .addHeader("RANGE", "bytes=" + startByte + "-" + length)//指定文件下载字节范围
                                        .build();
                                okHttpClient.newCall(request).enqueue(new DownloadCallbackWrapper(true, downloadCallBack));
                            } else if (startByte == length) {
                                //已下载完成
                                isDownloading = false;
                                if (downloadCallBack != null) {
                                    executor.execute(() -> downloadCallBack.onSuccess(file));
                                }
                            } else {
                                download();
                            }
                        }
                    } catch (Exception e) {
                        isDownloading = false;
                        executor.execute(() -> this.callback.onFailure(e.getMessage()));
                        if (download && downloadCallBack != null) {
                            executor.execute(() -> downloadCallBack.onFailure(e.getMessage()));
                        }
                    }
                } else {
                    isDownloading = false;
                    executor.execute(() -> this.callback.onFailure("Content-Length is empty"));
                    if (download && downloadCallBack != null) {
                        executor.execute(() -> downloadCallBack.onFailure("Content-Length is empty"));
                    }
                }
            } else {
                isDownloading = false;
                executor.execute(() -> this.callback.onFailure(response.message()));
                if (download && downloadCallBack != null) {
                    executor.execute(() -> downloadCallBack.onFailure(response.message()));
                }
            }
        }
    }

    private String getDefaultFileName(Response response) {
        String headerName = getHeaderFileName(response);
        if (!TextUtils.isEmpty(headerName)) {
            return headerName;
        } else {
            String urlName = getUrlFileName(url);
            return !TextUtils.isEmpty(urlName) ? urlName : System.currentTimeMillis() + "";
        }
    }

    /**
     * 解析文件头,获取文件名
     * Content-Disposition:attachment;filename=FileName.txt
     * Content-Disposition: attachment; filename*="UTF-8''%E6%9B%BF%E6%8D%A2%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A.pdf"
     */
    public static String getHeaderFileName(Response response) {
        if (response == null) {
            return "";
        }
        String disposition = response.header("Content-Disposition");
        if (TextUtils.isEmpty(disposition)) {
            return "";
        }
        String fileName = "";
        String[] parts = disposition.split(";");
        for (String part:parts) {
            if (TextUtils.isEmpty(part)) {
                continue;
            }
            if (part.contains("filename=")) {
                fileName = part.replace("filename=", "");
                break;
            }
            if (part.contains("filename*=")) {
                fileName = part.replace("filename*=", "");
                break;
            }
        }
        //去掉全部可能出现的其他字符
        fileName = fileName.replace("utf-8", "").replace("UTF-8", "")
                .replace("''", "").replace("\"", "").trim();
        try {
            //还原被编码的文件名
            fileName = URLDecoder.decode(fileName, "UTF-8");
        } catch (UnsupportedEncodingException ignored) {}
        return fileName;
    }

    /**
     * 从url中获取文件名
     * @param url
     * @return
     */
    public static String getUrlFileName(String url) {
        if (TextUtils.isEmpty(url)) {
            return "";
        }
        String name = url.substring(url.lastIndexOf('/') + 1);
        if (name.contains("?")) {
            return name.substring(0, name.indexOf('?'));
        }
        return name;
    }
}
